/*
 * Copyright 2019-2025 the original author or authors.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.iiifi.kite.launcher.runer;

import java.util.*;
import java.util.function.Function;

import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.*;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import com.iiifi.kite.launcher.enums.KiteEnv;
import com.iiifi.kite.launcher.enums.KiteLogLevel;
import com.iiifi.kite.launcher.service.LauncherService;

import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;

/**
 * 项目启动器，搞定环境变量问题
 *
 * @author kite@iiifi.com 花朝
 * @desc
 */
@Slf4j
@UtilityClass
public final class KiteApplication {

    /**
     * 代码部署于 linux 上，工作默认为 mac 和 Windows
     */
    private static final String OS_NAME_LINUX = "LINUX";

    /**
     * Create an application context
     *
     * @param appName application name
     * @param source The sources
     * @param args args the command line arguments
     * @return an application context created from the current state
     */
    public static ConfigurableApplicationContext run(String appName, Class source, String... args) {
        SpringApplicationBuilder builder = createSpringApplicationBuilder(appName, source, args);
        return builder.run(args);
    }

    private static SpringApplicationBuilder createSpringApplicationBuilder(String appName, Class source,
            String... args) {
        Assert.hasText(appName, "args appName is blank");
        // 读取环境变量，使用spring boot的规则
        ConfigurableEnvironment env = new StandardEnvironment();
        MutablePropertySources propertySources = env.getPropertySources();
        propertySources.addFirst(new SimpleCommandLinePropertySource(args));
        propertySources.addLast(new MapPropertySource(StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME,
                env.getSystemProperties()));
        propertySources.addLast(new SystemEnvironmentPropertySource(
                StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, env.getSystemEnvironment()));
        // 获取配置的环境
        List<String> activeProfiles = Arrays.asList(env.getActiveProfiles());
        Function<Object[], String> joinFun = StringUtils::arrayToCommaDelimitedString;
        SpringApplicationBuilder builder = new SpringApplicationBuilder(source);
        String profile;
        if (activeProfiles.isEmpty()) {
            // 默认dev开发
            profile = KiteEnv.DEV.getName();
            builder.profiles(profile);
        } else if (activeProfiles.size() == 1) {
            profile = activeProfiles.get(0);
        } else {
            // 同时存在多个环境变量
            throw new RuntimeException("同时存在多个环境变量:[" + joinFun.apply(activeProfiles.toArray()) + "]");
        }

        // 添加启动目录打印
        String startJarPath = KiteApplication.class.getResource("/").getPath().split("!")[0];
        String activePros = joinFun.apply(activeProfiles.toArray());
        System.err
                .println(String.format("---[%s]---启动中，读取到的环境变量:[%s]，jar地址:[%s]---", appName, activePros, startJarPath));
        boolean isLocalDev = KiteApplication.isLocalDev();

        // 系统设置 yml 配置无效
        System.setProperty("kite.env", profile);
        System.setProperty("kite.name", appName);
        System.setProperty("spring.application.name", appName);
        // 默认的属性配置，级别低于 yml
        Properties defaultProperties = new Properties();
        defaultProperties.setProperty("kite.is-local", String.valueOf(isLocalDev));
        defaultProperties.setProperty("spring.banner.location", "classpath:banner.txt");
        // dev环境本机不向注册中心注册
        if (isLocalDev && KiteEnv.DEV.name().equals(profile)) {
            defaultProperties.setProperty("spring.cloud.nacos.discovery.register-enabled", "false");
        }
        // KiteErrorStream 配置
        defaultProperties.setProperty("spring.cloud.stream.bindings.kite-error-in.destination", "kite-error");
        defaultProperties.setProperty("spring.cloud.stream.bindings.kite-error-out.destination", "kite-error");

        // 预设请求日志级别
        KiteEnv kiteEnv = KiteEnv.of(profile);
        // 使用 builder 的 props，优先级低，kite.log.request.level=xxx
        defaultProperties.setProperty(KiteLogLevel.REQ_LOG_PROPS_PREFIX + ".level", kiteEnv.getReqLogLevel().name());
        // 加载自定义组件
        ServiceLoader<LauncherService> loader = ServiceLoader.load(LauncherService.class);
        KiteApplication.toList(loader).stream().sorted()
                .forEach(launcherService -> launcherService.launcher(builder, env, appName, kiteEnv, isLocalDev));
        builder.properties(defaultProperties);
        return builder;
    }

    /**
     * 判断是否为本地开发环境
     *
     * @return boolean
     */
    private static boolean isLocalDev() {
        String osName = System.getProperty("os.name");
        return StringUtils.hasText(osName) && !(OS_NAME_LINUX.equals(osName.toUpperCase()));
    }

    /**
     * Iterable 转换为List集合
     *
     * @param elements Iterable
     * @param <E> 泛型
     * @return 集合
     */
    @SuppressWarnings("unchecked")
    private static <E> List<E> toList(Iterable<E> elements) {
        Objects.requireNonNull(elements, "elements es is null.");
        if (elements instanceof Collection) {
            return new ArrayList((Collection) elements);
        }
        Iterator<E> iterator = elements.iterator();
        List<E> list = new ArrayList<>();
        while (iterator.hasNext()) {
            list.add(iterator.next());
        }
        return list;
    }
}
